/*
	Logging SD Card Class

	This modules write data and initialised the SD memory.
*/

#include "globals.h"
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <util/delay.h>
#include <string.h>
#include "logging_sdcard.h"
#include "layer_uart.h"
#include "layer_comm.h"
#include "spi.h"
#include "sd.h"

// ---------------------------------------------------------------------
// Macros

#define LOG_SECTOR_BNDRY	1024
#define LOG_PKT_TYPE		1
#define LOG_COMP_TYPE		2

#define SAME				0x80
#define DIFF				0x00

// ---------------------------------------------------------------------
// Typedefs

// _typLogHdr | Packet Data | ftr
typedef struct _typLogHdr
{
	uint32_t	dts;
	uint32_t	nLogNo;
	uint32_t	nSeqNo;
	uint32_t	nTicks;
	uint8_t		nPktType;
	uint16_t	nPktSize;	
} _typLogHdr;

typedef struct _typLogSec
{
	uint8_t		nPktType;
	uint16_t	nPktSize;
} _typLogSec;

typedef struct _typLogFtr
{
	uint16_t	nCrc;
} _typLogFtr;

typedef struct _typLogData
{
	uint32_t	crntDts;		/* Current Date/Time stamp */
	uint32_t	nCrntLogNo;		/* Current Log Number */
	uint32_t	nCrntSeqNo;		/* Current Sequence Number */
	uint32_t	nCrntSecNo;		/* Current Sector addres */
	uint32_t	nRdSeekSecNo;	/* Seek Position */
	uint32_t	nRdSeekSeqNo;	/* Sequence No */
	uint32_t	nRdSeekLogNo;	/* Seek Log No */
	uint32_t	nMaxSecNo;		/* Maximum SD-Card Sector # */
	uint8_t		nEraseSecNo;	/* Number of blocks erased per secotr */
} _typLogData;

// ---------------------------------------------------------------------
// Private Functions
uint8_t		log_search_blank(void);
uint8_t		log_init_sdcard(void);

// ---------------------------------------------------------------------
// Static Varialbes
static struct _typLogData	typLogData =
{
	0,0,0,0,0,0,0,0,0
};
static uint8_t				uchReadBuf[512];

// ---------------------------------------------------------------------
// Functions Bodies
uint8_t log_init(uint32_t dts)
{
	uint8_t	bStatus = FALSE;

	typLogData.crntDts		= dts;
	typLogData.nCrntLogNo	= 0;
	typLogData.nCrntSeqNo	= 0;
	typLogData.nCrntSecNo	= 0;

	typLogData.nRdSeekLogNo = 0;
	typLogData.nRdSeekSecNo = 0;
	typLogData.nRdSeekSeqNo = 0;
	typLogData.nMaxSecNo	= 0;

	bStatus = log_init_sdcard();
	
	if (bStatus)
	{
		bStatus = log_search_blank();
	}
	
	return bStatus;
}

uint8_t log_write_entry(uint8_t* pchBuffer, uint16_t nSize, uint32_t nTicks)
{
	if (typLogData.nMaxSecNo && typLogData.nCrntSecNo < typLogData.nMaxSecNo )
	{
		uint8_t		uchData[512];
		_typLogHdr	*ptypHdr = (_typLogHdr*)uchData;
		uint16_t	nFulSize = nSize + sizeof(_typLogHdr);

		// Setup the header stuff
		ptypHdr->dts		= typLogData.crntDts;
		ptypHdr->nLogNo		= typLogData.nCrntLogNo;
		ptypHdr->nTicks		= nTicks;
		ptypHdr->nPktType	= LOG_PKT_TYPE;
		ptypHdr->nPktSize	= nSize;
		ptypHdr->nSeqNo		= typLogData.nCrntSeqNo;

		// copy into our temp buffer
		memcpy(&uchData[sizeof(_typLogHdr)],pchBuffer, nSize);

		// Do the compression if not first packet
		if (typLogData.nCrntSeqNo)
		{
			_typLogSec	*ptypSec = (_typLogSec*)&uchData[nFulSize];
			uint8_t		*pchDiff = &uchData[nFulSize + sizeof(_typLogSec)];
			uint8_t		nMode = DIFF;
			uint8_t		nLikeCnt = 0;
			uint8_t		i = 0;
			uint8_t		nSndCnt = 0;

			nSize += sizeof(_typLogHdr);

			// Do the compression
			do
			{
				switch (nMode)
				{
				case SAME:
					if (uchData[i] != uchReadBuf[i]
					|| i == (nSize-1))
					{
						if (nLikeCnt)
						{
							*pchDiff = SAME | (nLikeCnt - 1);
							pchDiff++;
							nSndCnt++;
						}

						nMode = DIFF;
						nLikeCnt = 1;
					}
					else if (nLikeCnt == 128)
					{
						*pchDiff = SAME | (nLikeCnt - 1);
						pchDiff++;
						nSndCnt++;
						nLikeCnt = 1;
					}
					else
					{
						nLikeCnt++;
					}
					break;
				case DIFF:
					if ((uchData[i] == uchReadBuf[i] 
						&& (i < (nSize-1) && uchData[i+1] == uchReadBuf[i+1]))
					||	i == (nSize-1))
					{
						// Save the difference counter
						if (nLikeCnt)
						{
							*pchDiff = nLikeCnt - 1;
							pchDiff++;
							nSndCnt++;
							// Copy in the differences
							memcpy(pchDiff,&uchData[i-nLikeCnt],nLikeCnt);

							// Increment the counter
							pchDiff+= nLikeCnt;	
							nSndCnt+= nLikeCnt + 1;
						}

						nMode = SAME;
						nLikeCnt = 1;
					}
					else if (nLikeCnt == 128)
					{
						*pchDiff = nLikeCnt - 1;
						pchDiff++;
						nSndCnt++;
						// Copy in the differences
						memcpy(pchDiff,&uchData[i-nLikeCnt],nLikeCnt);

						// Increment the counter
						pchDiff+= nLikeCnt;	
						nSndCnt+= nLikeCnt + 1;
						nLikeCnt = 1;
					}
					else
					{
						nLikeCnt++;
					}
					break;
				}
			} while (++i < nSize);

			// Insert the packet information
			ptypSec->nPktType	= LOG_COMP_TYPE;
			ptypSec->nPktSize	= nSndCnt;

			// Increment the counter
			nFulSize += nSndCnt + + sizeof(_typLogSec);
		}
		
		if (SDWriteFromSector(typLogData.nCrntSecNo, uchData, nFulSize, NULL, 0))
		{
			// mem copy to the reserved
			memcpy(uchReadBuf,uchData,nFulSize);

			typLogData.nCrntSecNo++; /* Increment the sector number */
			typLogData.nCrntSeqNo++; /* Increment the sequence number */

			return TRUE;
		}
	}

	return FALSE;
}

uint8_t	log_write(uint8_t* pchBuffer, uint16_t nSize)
{
	return TRUE;
}

uint8_t		log_init_sdcard(void)
{
	if (SDInit())
	{
		typLogData.nMaxSecNo = SDGetSize();

		return TRUE;
	}

	return FALSE;
}

uint8_t		log_search_blank(void)
{
	uint8_t		bStatus = eLogFail;
	uint32_t	nBlkNo=0;
	uint32_t	nLogNo=0;
	uint32_t	nMaxBlk = typLogData.nMaxSecNo;
	_typLogHdr	typHdr;

	if (typLogData.nMaxSecNo)
	{
		uint32_t	nOffset = 0;
		uint32_t	nSize = typLogData.nMaxSecNo ;

		// Divide and Conqure
		do
		{
			// Divide Size by One
			nSize /= 2;

			if (SDReadFromSector((nOffset + nSize)*(uint32_t)LOG_SECTOR_BNDRY,(uint8_t*)&typHdr, sizeof(_typLogHdr),0))
			{
				if (typHdr.dts == typLogData.crntDts)
				{
					nOffset += nSize;
				}
			}
		} while (nSize > 16);


		// For each block numbers, check the header for a non-match to the dts
		for (nBlkNo = nOffset; nBlkNo < nMaxBlk && !bStatus; nBlkNo++)
		{
			if (SDReadFromSector(nBlkNo*(uint32_t)LOG_SECTOR_BNDRY,(uint8_t*)&typHdr, sizeof(_typLogHdr),0))
			{
				if (typHdr.dts != typLogData.crntDts)
				{
					bStatus = eLogOk;
					typLogData.nCrntSecNo = nBlkNo * (uint32_t)LOG_SECTOR_BNDRY;
					typLogData.nCrntLogNo = nLogNo + 1;
					typLogData.nCrntSeqNo = 0;
				}
				else
				{
					nLogNo = typHdr.nLogNo;
				}
			}
			else
			{
				bStatus = eLogSdRdFail;
				break;
			}
		}

		if (nBlkNo >= nMaxBlk)
			return eLogNoSpace;
		else
			return bStatus;
	}
	else
	{
		return eLogNoCard;
	}
}

uint8_t log_read_report_usage(void)
{
	uint8_t bStatus = FALSE;
	_typSdCID data;
	_typLoggCommReport typReport = { eMsgIdGeneralSpecific, typLogData.crntDts, typLogData.nCrntLogNo, typLogData.nCrntSecNo, typLogData.nCrntSeqNo};

	if (typLogData.nMaxSecNo)
	{
		SDGetCID(&data);
	}
	
	comm_new(eMsgPc,eMsgAtmel,sizeof(data)+sizeof(typReport));
	comm_append((uint8_t*)&typReport,sizeof(typReport));
	comm_append((uint8_t*)&data,sizeof(data));
	comm_send();

	bStatus = TRUE;

	return bStatus;
}

uint8_t	log_read_report_info(uint32_t nLogNo)
{
	uint8_t bStatus = FALSE;
	_typLogHdr	typHdr;
	_typLoggCommReport typReport = { eMsgIdGeneralSpecific, typLogData.crntDts,nLogNo, 0, 0};
	uint32_t	nMaxBlk = typLogData.nMaxSecNo;
	uint32_t	nBlkNo =0;
	uint32_t	nChkNo = 0;
	uint32_t	i;

	if (typLogData.nMaxSecNo && log_read_seek(nLogNo,0))
	{
		for ( nBlkNo = (typLogData.nRdSeekSecNo + LOG_SECTOR_BNDRY); nBlkNo < nMaxBlk ; nBlkNo++)
		{
			if (SDReadFromSector(nBlkNo,(uint8_t*)&typHdr, sizeof(_typLogHdr),0))
			{
				if (typHdr.dts != typLogData.crntDts
				||	typHdr.nLogNo != nLogNo)
				{
					// FIX THIS STUFF
					uint32_t	nOffset = 0;
					uint16_t	nSize = LOG_SECTOR_BNDRY;

					nChkNo = nBlkNo - LOG_SECTOR_BNDRY;

					// Divide and Conqure
					do
					{
						// Divide Size by One
						nSize /= 2;

						if (SDReadFromSector((nOffset + nSize + nChkNo)*(uint32_t)LOG_SECTOR_BNDRY,(uint8_t*)&typHdr, sizeof(_typLogHdr),0))
						{
							if (typHdr.dts == typLogData.crntDts
							&& typHdr.nLogNo == nLogNo)
							{
								nOffset += nSize;
							}
						}
					} while (nSize > 16);

					// work backwards now...
					for (i=nOffset+1;i<LOG_SECTOR_BNDRY;i++)
					{
						if (SDReadFromSector(nChkNo+i,(uint8_t*)&typHdr, sizeof(_typLogHdr),0))
						{
							if (typHdr.dts != typLogData.crntDts
							||	typHdr.nLogNo != nLogNo)
							{
								typReport.nCrntSeqNo = nChkNo - typLogData.nRdSeekSecNo + i; 
								typReport.nCrntSector = typLogData.nRdSeekSecNo;

								comm_new(eMsgPc,eMsgAtmel,sizeof(typReport));
								comm_append((uint8_t*)&typReport,sizeof(typReport));
								comm_send();

								return TRUE;
							}
						}
						else
						{
							comm_response(eCmdRespSdReadFail);
							return FALSE;
						}
					}
				}
			}
			else
			{
				comm_response(eCmdRespSdReadFail);
				return FALSE;
			}
		}
	}
	else
	{
		comm_response(eCmdRespFailed);
	}

	return bStatus;
}

uint8_t	log_read_seek(uint32_t nLogNo, uint32_t nSeqNo)
{
	uint8_t		bStatus = FALSE;
	uint32_t	nBlkNo =0;
	uint32_t	nMaxBlk = typLogData.nMaxSecNo;
	_typLogHdr	typHdr;

	if(typLogData.nMaxSecNo)
	{
		// If we have already seeked somewhere, and say we are a log behind or more!
		if (typLogData.nRdSeekLogNo == nLogNo)
		{
			nBlkNo = (typLogData.nRdSeekSecNo - typLogData.nRdSeekSeqNo + nSeqNo) / LOG_SECTOR_BNDRY;
		}
		else 
		{
			uint32_t	nOffset;
			uint32_t	nSize;

			nBlkNo = 0;

			if (typLogData.nRdSeekLogNo < nLogNo)
			{
				nBlkNo = typLogData.nRdSeekSecNo / LOG_SECTOR_BNDRY;
			}

			// Initialise Position
			nOffset = nBlkNo;
			nSize = typLogData.nMaxSecNo - nBlkNo;

			// Divide and Conqure
			do
			{
				// Divide Size by One
				nSize /= 2;

				if (SDReadFromSector((nOffset + nSize)*(uint32_t)LOG_SECTOR_BNDRY,(uint8_t*)&typHdr, sizeof(_typLogHdr),0))
				{
					if (typHdr.dts == typLogData.crntDts)
					{
						if ( typHdr.nLogNo < nLogNo ) // Still not there
						{
							nOffset += nSize;
						}
						else if (typHdr.nLogNo == nLogNo)	// Found Log Number
						{
							nOffset += nSize - (typHdr.nSeqNo / LOG_SECTOR_BNDRY);
							break;
						}
					}
				}
			} while (nSize > 16);

			// Save the result
			nBlkNo = nOffset;
		}

		// For each block numbers, check the header for a non-match to the dts
		for ( ; nBlkNo < nMaxBlk ; nBlkNo++)
		{
			if (SDReadFromSector(nBlkNo*LOG_SECTOR_BNDRY,(uint8_t*)&typHdr, sizeof(_typLogHdr),0))
			{
				// Find the Log Number
				if (typHdr.dts == typLogData.crntDts
				&&	typHdr.nLogNo == nLogNo)
				{
					// Find the sequence number
					if (SDReadFromSector((nBlkNo*LOG_SECTOR_BNDRY)+nSeqNo,(uint8_t*)&typHdr, sizeof(_typLogHdr),0))
					{
						if (typHdr.dts == typLogData.crntDts
						&& typHdr.nLogNo == nLogNo
						&& typHdr.nSeqNo == nSeqNo)
						{
							typLogData.nRdSeekSecNo = (nBlkNo * (uint32_t)LOG_SECTOR_BNDRY) + (uint32_t)nSeqNo;
							typLogData.nRdSeekLogNo = nLogNo;
							typLogData.nRdSeekSeqNo = nSeqNo;

							return TRUE;
						}			
						else
						{
							return FALSE;
						}
					}
					else
					{
						return FALSE;
					}
				}
				else if (typHdr.dts != typLogData.crntDts)
				{
					return FALSE;
				}
			}
			else
			{
				return FALSE;
			}
		}
	}

	return bStatus;
}

uint8_t log_read_next(void)
{
	_typLogHdr	typHdr;
	uint8_t		bStatus = FALSE;

	if (typLogData.nMaxSecNo && SDReadFromSector(typLogData.nRdSeekSecNo,(uint8_t*)&typHdr, sizeof(_typLogHdr),0)
	&&	typHdr.dts == typLogData.crntDts
	&&	typHdr.nLogNo == typLogData.nRdSeekLogNo
	&&	typHdr.nSeqNo == typLogData.nRdSeekSeqNo
	&&	typHdr.nPktSize > 0 && typHdr.nPktSize < 512)
	{
		_typLoggCommHdr typComHdr;
		uint16_t	nSize = typHdr.nPktSize+sizeof(_typLogHdr);

		// Read the entire packet
		if (SDReadFromSector(typLogData.nRdSeekSecNo,uchReadBuf, nSize,0))
		{
			typComHdr.nId = eMsgIdLogData;

			// Create the packet
			comm_new(eMsgPc, eMsgAtmel, sizeof(_typLoggCommHdr) + nSize);
			comm_append((uint8_t*)&typComHdr, sizeof(_typLoggCommHdr));
			comm_append(uchReadBuf, nSize);
			comm_send();

			// Increment the counters etc
			typLogData.nRdSeekSecNo++;
			typLogData.nRdSeekSeqNo++;

			bStatus = TRUE;
		}
	}

	return bStatus;
}

uint8_t log_read_compressed_next(void)
{
	_typLogHdr	typHdr;
	uint8_t		bStatus = FALSE;

	if (typLogData.nMaxSecNo && SDReadFromSector(typLogData.nRdSeekSecNo,(uint8_t*)&typHdr, sizeof(_typLogHdr),0)
	&&	typHdr.dts == typLogData.crntDts
	&&	typHdr.nLogNo == typLogData.nRdSeekLogNo
	&&	typHdr.nSeqNo == typLogData.nRdSeekSeqNo
	&&	typHdr.nPktSize > 0 && typHdr.nPktSize < 512)
	{
		uint16_t	nSize = typHdr.nPktSize+sizeof(_typLogHdr);
		_typLogSec	typLogSec;

		// Read the entire packet
		if (SDReadFromSector(typLogData.nRdSeekSecNo,(uint8_t*)&typLogSec, sizeof(_typLogSec),nSize))
		{
			uint8_t		uchData[typLogSec.nPktSize];

			if (typLogSec.nPktType == LOG_COMP_TYPE	&&	typLogSec.nPktSize
			&& SDReadFromSector(typLogData.nRdSeekSecNo,uchData,typLogSec.nPktSize,nSize+sizeof(_typLogSec)))
			{
				_typLoggCommHdr typComHdr;
				typComHdr.nId = eMsgIdLogData;

				// Create the packet
				comm_new(eMsgPc, eMsgAtmel, sizeof(_typLoggCommHdr) + typLogSec.nPktSize);
				comm_append((uint8_t*)&typComHdr, sizeof(_typLoggCommHdr));
				comm_append(uchData, typLogSec.nPktSize);
				comm_send();

				// Increment the counters etc
				typLogData.nRdSeekSecNo++;
				typLogData.nRdSeekSeqNo++;

				bStatus = TRUE;
			}

		}
	}

	return bStatus;
}
